iT邦幫忙

2021 iThome 鐵人賽

DAY 21
0
永豐金融APIs

永豐金融APIs - 從零開始到放棄!?系列 第 21

Backtrader - 指標使用

  • 分享至 

  • xImage
  •  

以下內容皆參考 Backtrader 官網

在評估股票的時候,我們常常會用一些指標來輔助,今天來介紹的是移動平均線,像是 5日線、月線、季線...等。在 backtrader 裡,移動平均線也和 Open, High, Low, Close...等一樣的,我們可以在某個時間點,取出對應的值來進行評估。

程式碼如下:

class TestStrategy(bt.Strategy):
    #...略
    
    def __init__(self):
        #...略
        
        # period 就是我們要取幾日去做平均
        self.sma = bt.indicators.SimpleMovingAverage(self.data, period=5)

如果想要懶一點,self.data 預設就是第一組數據,除非你不是用第一組,不然可以省略

假設我們今天要做一個策略如下:

  • 價格漲破 5日線就買入
  • 價格跌破 5日線就賣出
  • 一買一賣

程式碼如下:

class TestStrategy(bt.Strategy):
    #...略
    
    def next(self):
        self.log("收盤價: %.2f" % self.dataclose[0])
        
        if self.order:
            return
            
        if not self.position:
            if self.dataclose[0] > self.sma[0]:
                self.log("買入 %.2f" % self.dataclose[0])
                self.order = self.buy()
        else:
            if self.dataclose[0] < self.sma[0]:
                self.log("賣出 %.2f" % self.dataclose[0])
                self.order = self.sell()

這邊如果你有下去跑,而且輸出結果來看的話,會發現它起始的日期不是 2020-01-02,因為移動平均線是前幾是的收盤價的平圴,所以它會累積到足夠的數據才會開始執行回測的策略。

再來,配合昨天的參數化,把移動平均線參數化

class TestStrategy(bt.Strategy):
    params = (
        ( "maperiod", 5 ),
        ( "defaultSize", 1000 ),
        ( "printLog", False ),
    )
    
    #...略
    
    def __init__(self):
        self.dataclose[0] = self.data.close
        self.sizer.setsizing(self.p.defaultSize)
        self.sma = bt.indicators.SimpleMovingAverage(period = self.p.maperiod)
        
        self.order = None

有了參數化之後,我們還可以進行參數的最佳化,以移動平均來說,要用 5日、月線或者是季線來當我們的指標呢?這裡就可以指定多組參數,讓 backtrader 去執行回測,我們就可以看到不同值結果。

cerebro.optstrategy(
    TestStrategy,
    maperiod=range(10, 31)
)

因為我們有多組的參數去跑回測,所以我們關注的結果就是每個參數的結果,要取得這個結果,可以利用策略裡的 stop 事件,當回測跑完之後就會去執行這一個程式

class TestStrategy(bt.Strategy):
    #...略
    def stop(self):
        self.log("MA Period %2d 結束收益 %.2f" % (self.p.maperiod, self.broker.getvalue()), doPrint = True)

完整程式碼如下:

'''
- 低於 均線 買入
- 高於 均線 賣出
- 一買一賣
'''
import backtrader as bt

class TestStrategy(bt.Strategy):

    params = (
        ('maperiod', 15),
        ('defaultSize', 1000),
        ('printlog', False),
    )

    def log(self, txt, dt = None, doPrint = False):
        ''' Log function for this strategy '''
        if self.params.printlog or doPrint:
            dt = dt or self.datas[0].datetime.date(0)
            print("%s %s" % (dt.isoformat(), txt))

    def __init__(self):
        self.dataclose = self.data.close
        self.sizer.setsizing(self.p.defaultSize)
        self.sma = bt.indicators.SimpleMovingAverage(period = self.p.maperiod)

        # pendding order 
        self.order = None


    def notify_order(self, order):        
        if order.status in [order.Submitted, order.Accepted]:
            return

        if order.status in [order.Completed]:
            if order.isbuy():
                self.log("買入 價格: %.2f 成本: %.2f 手續費: %.2f" % (order.executed.price, order.executed.value, order.executed.comm))
            elif order.issell():
                self.log("賣出 價格: %.2f 成本: %.2f 手續費: %.2f" % (order.executed.price, order.executed.value, order.executed.comm))

            self.bar_executed = len(self)

        elif order.status in [order.Canceled, order.Margin, order.Rejected]:
            self.log("交易取消/餘額不足/拒絕交易")

        self.order = None

    def notify_trade(self, trade):
        if not trade.isclosed:
            return

        self.log("交易收益: 毛利 %.2f 淨利 %.2f" % (trade.pnl, trade.pnlcomm))

    def next(self):
        self.log("收盤價: %.2f" % self.dataclose[0])

        if self.order:
            return

        if not self.position:

            if self.dataclose[0] > self.sma[0]:
                self.log("買入 %.2f" % self.dataclose[0])
                self.order = self.buy()

        else:

            if self.dataclose[0] < self.sma[0]:
                self.log("賣出 %.2f" % self.dataclose[0])
                self.order = self.sell()

    def stop(self):
        self.log('MA Period %2d  結束收益 %.2f' % (self.params.maperiod, self.broker.getvalue()), doPrint = True)
        
        

cerebro = bt.Cerebro()

# set strategy
cerebro.optstrategy(
    TestStrategy,
    maperiod = range(10, 31)
)

# set cash 
cerebro.broker.setcash(10**6)

# set commission
cerebro.broker.setcommission(commission=0.1425/100)

data = bt.feeds.PandasData(dataname=df, timeframe=bt.TimeFrame.Minutes)

cerebro.resampledata(data, timeframe=bt.TimeFrame.Days)

cerebro.run()


上一篇
Backtrader - 策略收益
下一篇
Backtrader - 繪圖
系列文
永豐金融APIs - 從零開始到放棄!?30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言